/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.analyzer;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.javaruntype.exceptions.TypeRecognitionException;
import org.javaruntype.type.Type;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.ImportMap;
import org.jfab.core.collection.ParameterNameList;
import org.jfab.core.factory.CGAnnotationFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGAnnotation;
import org.jfab.core.model.CGObject;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.TypeFactory;

/**
 * Provides base functionality for an analyzer.
 * 
 * @param <T> Return type.
 * @param <U> Source type.
 */
public abstract class AbstractAnalyzer<T extends CGObject, U> implements
        Analyzer<T, U>
{
    /** Line separator. */
    protected static final String LINE_SEPARATOR = System
            .getProperty("line.separator");

    /** Annotation factory. */
    private final CGAnnotationFactory _annotationFactory;

    /** Code generator type factory. */
    private final CGTypeFactory _cgTypeFactory;

    /** Comparator. */
    private final Comparator<T> _comparator;

    /** String factory. */
    private final StringFactory _stringFactory;

    /** Code generator type factory. */
    private final TypeFactory _typeFactory;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /**
     * Construct this object with the given parameters.
     * 
     * @param typeFactory Type factory.
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory.
     * @param stringFactory String factory.
     * @param annotationFactory Annotation factory.
     * @param comparator Comparator.
     */
    public AbstractAnalyzer(final TypeFactory typeFactory,
            final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final StringFactory stringFactory,
            final CGAnnotationFactory annotationFactory,
            final Comparator<T> comparator)
    {
        _typeFactory = typeFactory;
        _cgTypeUtils = cgTypeUtils;
        _cgTypeFactory = cgTypeFactory;
        _stringFactory = stringFactory;
        _annotationFactory = annotationFactory;
        _comparator = comparator;
    }

    /**
     * @see org.jfab.core.analyzer.Analyzer#analyze(U[], java.lang.String)
     */
    @Override
    public final List<T> analyze(final U[] objects, final String javaSource)
    {
        final Comparator<T> comparator = getComparator();
        final Set<T> set = new TreeSet<T>(comparator);

        for (final U object : objects)
        {
            final T myObject = analyze(object, javaSource);

            if (myObject != null)
            {
                set.add(myObject);
            }
        }

        final List<T> answer = new ArrayList<T>();
        answer.addAll(set);

        return answer;
    }

    /**
     * @param javaSource Java source.
     * 
     * @return a map of simple class name to type.
     */
    protected final ImportMap analyzeImports(final String javaSource)
    {
        final ImportMap answer = new ImportMap();

        if (StringUtils.isNotEmpty(javaSource))
        {
            final String key0 = "import ";
            int index0 = javaSource.indexOf(key0);
            final String key1 = ";";

            while (index0 >= 0)
            {
                final int index1 = javaSource.indexOf(key1, index0 + 1);

                if (index1 > index0)
                {
                    final String className = javaSource.substring(
                            index0 + key0.length(), index1);

                    if (!className.contains("+"))
                    {
                        final CGType type = getCgTypeFactory().createType(
                                className);
                        final String shortName = _cgTypeUtils.getType(type)
                                .getSimpleName();
                        answer.put(shortName, type);
                    }
                }

                index0 = javaSource.indexOf(key0, index0 + 1);
            }
        }

        return answer;
    }

    /**
     * @param commentLine Comment line.
     * 
     * @return a cleaned up comment line.
     */
    protected final String cleanupCommentLine(final String commentLine)
    {
        String answer = commentLine;

        answer = answer.trim();
        answer = StringUtils.substringAfter(answer, "*");
        answer = answer.trim();

        return answer;
    }

    /**
     * @param javaLangTypes Java reflection types.
     * 
     * @return a new list of types.
     */
    protected final CGTypeList createListType(
            final java.lang.reflect.Type[] javaLangTypes)
    {
        final int parameterOffset = 0;

        return createListType(javaLangTypes, parameterOffset);
    }

    /**
     * @param javaLangTypes Java reflection types.
     * @param parameterOffset Parameter offset.
     * 
     * @return a new list of types.
     */
    protected final CGTypeList createListType(
            final java.lang.reflect.Type[] javaLangTypes,
            final int parameterOffset)
    {
        final CGTypeList answer = new CGTypeList();
        final int size = javaLangTypes.length;

        for (int i = parameterOffset; i < size; i++)
        {
            final java.lang.reflect.Type type = javaLangTypes[i];
            answer.add(getCgTypeFactory().createType(type));
        }

        return answer;
    }

    /**
     * @param javaSource Java source.
     * 
     * @return annotations.
     */
    protected final CGAnnotationList getAnnotations(final String javaSource)
    {
        final CGAnnotationList answer = new CGAnnotationList();

        String text = javaSource;
        final ImportMap importMap = analyzeImports(text);

        final String key000 = ";";
        final int index000 = javaSource.lastIndexOf(key000);

        if (index000 >= 0)
        {
            text = javaSource.substring(index000 + 1);
        }

        final String key0 = "@";
        int index0 = text.indexOf(key0);

        while (index0 >= 0)
        {
            final int index1 = text.indexOf(' ', index0 + 1);

            if (index1 > index0)
            {
                String name = text.substring(index0 + 1, index1);

                final ParameterNameList attributes = new ParameterNameList();

                final int index00 = name.indexOf('(');
                final int index11 = name.indexOf(')');

                if ((index00 >= 0) && (index11 > index00))
                {
                    final String attrString = name.substring(index00 + 1,
                            index11);
                    name = name.substring(0, index00);
                    attributes.add(attrString);
                }

                CGType cgType = null;

                try
                {
                    final Type<?> type = getTypeFactory().createType(name);

                    if (type != null)
                    {
                        cgType = getCgTypeFactory().createType(type);
                    }
                }
                catch (final TypeRecognitionException e)
                {
                    cgType = importMap.get(name);
                }

                if (cgType != null)
                {
                    final CGAnnotation cgAnnotation = getAnnotationFactory()
                            .createAnnotation(cgType, attributes);
                    answer.add(cgAnnotation);
                }
            }

            index0 = text.indexOf(key0, index0 + 1);
        }

        return answer;
    }

    /**
     * @return the cgTypeFactory
     */
    protected final CGTypeFactory getCgTypeFactory()
    {
        return _cgTypeFactory;
    }

    /**
     * @return the cgTypeUtils
     */
    protected CGTypeUtilities getCgTypeUtils()
    {
        return _cgTypeUtils;
    }

    /**
     * @return the comparator
     */
    protected final Comparator<T> getComparator()
    {
        return _comparator;
    }

    /**
     * @return the stringFactory
     */
    protected final StringFactory getStringFactory()
    {
        return _stringFactory;
    }

    /**
     * @param sourceCode Source code.
     * @param methodName Method name.
     * @param parameterTypes Parameter types.
     * @param throwsTypes Throws types.
     * @param isInterface Flag indicating if the method is in an interface.
     * 
     * @return the index of the specified method.
     */
    protected final int locateMethod(final String sourceCode,
            final String methodName, final CGTypeList parameterTypes,
            final CGTypeList throwsTypes, final boolean isInterface)
    {
        int answer = -1;

        final String endString = isInterface ? ";" : "\\{";
        final String regex = createMethodSignatureRegex(methodName,
                parameterTypes, throwsTypes, endString);

        final Pattern pattern = Pattern.compile(regex);
        int index = sourceCode.indexOf(methodName + "(");

        while ((index >= 0) && (answer < 0))
        {
            final Matcher matcher = pattern
                    .matcher(sourceCode.substring(index));

            if (matcher.find())
            {
                answer = index + matcher.start();
            }

            index = sourceCode.indexOf(methodName + "(", index + 1);
        }

        return answer;
    }

    /**
     * @param line Line.
     * 
     * @return the comment lines (if any) from the given parameter.
     */
    protected CommentList parseCommentLines(final String line)
    {
        CommentList answer = null;
        final String key0 = "/**";
        final int index0 = line.lastIndexOf(key0);

        if (index0 >= 0)
        {
            final String text = line.substring(index0);

            if (!text.contains(";"))
            {
                final String key1 = "*/";
                String comment = StringUtils.substringBetween(text, key0, key1);

                if (comment != null)
                {
                    answer = new CommentList();

                    final String key2 = "\n";

                    if (comment.contains(key2))
                    {
                        final String[] commentLines = comment.split(key2);

                        final int size = commentLines.length;

                        for (int i = 0; i < size; i++)
                        {
                            String commentLine = commentLines[i];
                            commentLine = cleanupCommentLine(commentLine);

                            if (((0 < i) && (i < (size - 1)))
                                    || (((i == 0) || (i == (size - 1))) && StringUtils
                                            .isNotEmpty(commentLine)))
                            {
                                answer.add(commentLine);
                            }
                        }
                    }
                    else
                    {
                        comment = comment.trim();
                        answer.add(comment);
                    }
                }
            }
        }

        return answer;
    }

    /**
     * @param sourceCode Source code.
     * 
     * @return package name.
     */
    protected final String parsePackageName(final String sourceCode)
    {
        final String answer = StringUtils.substringBetween(sourceCode,
                "package ", ";");

        return answer;
    }

    /**
     * @param methodName Method name.
     * @param parameterTypes Parameter types.
     * @param throwsTypes Throws types.
     * @param endString End string ('\\{' for class, ';' for interface).
     * 
     * @return a method signature regular expression.
     */
    private String createMethodSignatureRegex(final String methodName,
            final CGTypeList parameterTypes, final CGTypeList throwsTypes,
            final String endString)
    {
        final StringBuilder sb = new StringBuilder();
        sb.append("(?s)");
        sb.append("^");
        sb.append(methodName);
        sb.append("\\(");

        int size = parameterTypes.size();

        for (int i = 0; i < size; i++)
        {
            final CGType type = parameterTypes.get(i);
            final String typeName = getTypeName(type);

            sb.append("[a-zA-Z0-9 \n]*");
            sb.append(typeName);
            sb.append(" [a-zA-Z0-9]+");

            if (i < (size - 1))
            {
                sb.append(",[ ]*");
            }
        }

        sb.append("\\)");

        if (CollectionUtils.isNotEmpty(throwsTypes))
        {
            sb.append("[ \n]*throws[ \n]*");
            size = throwsTypes.size();

            for (int i = 0; i < size; i++)
            {
                final CGType throwsType = throwsTypes.get(i);
                final String typeName = getTypeName(throwsType);
                sb.append(typeName);

                if (i < (size - 1))
                {
                    sb.append(",[ ]*");
                }
            }
        }

        sb.append("[ \n]*").append(endString).append(".*");

        return sb.toString();
    }

    /**
     * @return the annotationFactory
     */
    private CGAnnotationFactory getAnnotationFactory()
    {
        return _annotationFactory;
    }

    /**
     * @return the typeFactory
     */
    private TypeFactory getTypeFactory()
    {
        return _typeFactory;
    }

    /**
     * @param type Type.
     * 
     * @return the type name.
     */
    private String getTypeName(final CGType type)
    {
        String answer = _cgTypeUtils.getType(type).getSimpleName();
        final String[] values = { "[", "]", "<", "?", ">", };
        answer = replaceAll(answer, values);

        return answer;
    }

    /**
     * @param string String.
     * @param values Replacement values.
     * 
     * @return the given string with the replacements made.
     */
    private String replaceAll(final String string, final String[] values)
    {
        String answer = string;

        for (final String value : values)
        {
            answer = answer.replaceAll("[\\" + value + "]", "\\\\" + value);
        }

        return answer;
    }
}
